﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.IO;
using System.Diagnostics;

namespace CS544_Protocol_Client
{
    public class Message
    {
        public MessageType Type { get; set; }
        public short Length { get; set; }
        public MessageBody Body { get; set; }
        public const byte ETX = (byte) 3;
        public const int HEADER_BYTE_LENGTH = 4;


        public static byte[] Serialize(Message msg)
        {
            //start by serializing the body.
            DataContractJsonSerializer ser = new DataContractJsonSerializer(MessageBodyFactory.Factory.GetBodyType(msg.Type));
            MemoryStream ms = new MemoryStream();
            ser.WriteObject(ms, msg.Body);
            string json = Encoding.ASCII.GetString(ms.ToArray());
            ms.Close();

            msg.Length = (short)(json.Length + 1);

            MemoryStream ws = new MemoryStream();

            BinaryWriter writer = new BinaryWriter(ws, Encoding.ASCII);

            byte[] msgType = BitConverter.GetBytes(Convert.ToInt16(msg.Type));
            byte[] msgLen = BitConverter.GetBytes(Convert.ToInt16(msg.Length));

            if (BitConverter.IsLittleEndian)
            {
                Array.Reverse(msgType);
                Array.Reverse(msgLen);
            }

            writer.Write(msgType);
            writer.Write(msgLen);
            writer.Write(json.ToCharArray());
            writer.Write(ETX);
            writer.Flush();

            byte[] data = ws.ToArray();
            ws.Close();

            return data;
        }

        public static Message Deserialize(string encodedMsg)
        {
            Message msg = new Message();
            //get the byte array of the message
            byte[] data = Encoding.ASCII.GetBytes(encodedMsg);

            //first byte is the MessageType
            short msgType = BitConverter.ToInt16(data, 0);
            short msgLen = BitConverter.ToInt16(data, 2);
            if (BitConverter.IsLittleEndian)
            {
                byte[] msgTypeBytes = BitConverter.GetBytes(msgType);
                Array.Reverse(msgTypeBytes);
                msgType = BitConverter.ToInt16(msgTypeBytes, 0);

                byte[] msgLenBytes = BitConverter.GetBytes(msgLen);
                Array.Reverse(msgLenBytes);
                msgLen = BitConverter.ToInt16(msgLenBytes, 0);
            }

            msg.Type = (MessageType)msgType;

            //second byte is the message length
            
            msg.Length = (short)msgLen;

            //loop through the rest of the byte array to get the message body.
            byte[] msgBody = new byte[data.Length-5];
            for (int i = 4; i < data.Length - 1; i++)
            {
                msgBody[i-4] = data[i];
            }

            //parse the message body.
            DataContractJsonSerializer ser = new DataContractJsonSerializer(MessageBodyFactory.Factory.GetBodyType(msg.Type));
            MemoryStream ms = new MemoryStream(msgBody);

            msg.Body = ser.ReadObject(ms) as MessageBody;
            ms.Close();

            return msg;
        }

        private static byte[] ReverseBytes(byte[] inArray)
        {
            byte temp;
            int highCtr = inArray.Length - 1;

            for (int ctr = 0; ctr < inArray.Length / 2; ctr++)
            {
                temp = inArray[ctr];
                inArray[ctr] = inArray[highCtr];
                inArray[highCtr] = temp;
                highCtr -= 1;
            }
            return inArray;
        }
    }

    public class MessageEventArgs : EventArgs
    {
        public Message Message;

        public MessageEventArgs(Message msg)
        {
            Message = msg;
        }
    }

    public class MessageBodyFactory
    {
        #region Singleton pattern implementation
        private static MessageBodyFactory factory;

        private MessageBodyFactory()
        {
            bodyMapping = new Dictionary<MessageType, Type>();
            
            //Populate the bodyMapping object
            bodyMapping.Add(MessageType.REGISTER, typeof(RegisterBody));
            bodyMapping.Add(MessageType.REGISTER_ACK, typeof(UserAckBody));
            bodyMapping.Add(MessageType.REGISTER_NAK, typeof(UserNakBody));
            bodyMapping.Add(MessageType.LOGIN, typeof(LoginBody));
            bodyMapping.Add(MessageType.LOGIN_ACK, typeof(UserAckBody));
            bodyMapping.Add(MessageType.LOGIN_NAK, typeof(UserNakBody));
            bodyMapping.Add(MessageType.LOGOUT, typeof(MessageBody));
            bodyMapping.Add(MessageType.LOGOUT_ACK, typeof(MessageBody));
            bodyMapping.Add(MessageType.LOGOUT_NAK, typeof(NakBody));
            bodyMapping.Add(MessageType.MESSAGE_OUT, typeof(MessageOutBody));
            bodyMapping.Add(MessageType.MESSAGE_OUT_ACK, typeof(MessageOutAckBody));
            bodyMapping.Add(MessageType.MESSAGE_OUT_NAK, typeof(MessageOutNakBody));
            bodyMapping.Add(MessageType.MESSAGE_IN, typeof(MessageInBody));
            bodyMapping.Add(MessageType.SET_STATUS, typeof(StatusBody));
            bodyMapping.Add(MessageType.SET_STATUS_ACK, typeof(MessageBody));
            bodyMapping.Add(MessageType.SET_STATUS_NAK, typeof(NakBody));
            bodyMapping.Add(MessageType.LIST_USERS, typeof(MessageBody));
            bodyMapping.Add(MessageType.LIST_USERS_ACK, typeof(ListUsersAckBody));
            bodyMapping.Add(MessageType.LIST_USERS_NAK, typeof(NakBody));

        }

        public static MessageBodyFactory Factory
        {
            get
            {
                if (MessageBodyFactory.factory == null)
                {
                    MessageBodyFactory.factory = new MessageBodyFactory();
                }
                return factory;
            }
        }
        #endregion

        private Dictionary<MessageType, Type> bodyMapping;

        public Type GetBodyType(MessageType type)
        {
            try
            {
                return bodyMapping[type];
            }
            catch (Exception e)
            {
                throw e;
            }
        }

    }

    #region MessageBody and its Derivatives

    [DataContract]
    [KnownType(typeof(NakBody))]
    [KnownType(typeof(RegisterBody))]
    [KnownType(typeof(UserAckBody))]
    [KnownType(typeof(MessageOutBody))]
    [KnownType(typeof(MessageOutAckBody))]
    [KnownType(typeof(MessageInBody))]
    [KnownType(typeof(ListUsersAckBody))]
    [KnownType(typeof(StatusBody))]
    public class MessageBody
    {
        public MessageBody()
        {
        }
    }

    [DataContract]
    [KnownType(typeof(UserNakBody))]
    [KnownType(typeof(MessageOutNakBody))]
    public class NakBody : MessageBody
    {
        [DataMember(Name = "ERROR")]
        public int ErrorCode { get; set; }

        [DataMember(Name = "DESC")]
        public string Description { get; set; }
    }

    [DataContract]
    [KnownType(typeof(LoginBody))]
    public class RegisterBody : MessageBody
    {
        [DataMember(Name = "USER")]
        public string Username { get; set; }

        [DataMember(Name = "PASS")]
        public string Password { get; set; }
    }

    [DataContract]
    public class UserAckBody : MessageBody
    {
        [DataMember(Name = "USER")]
        public string Username { get; set; }
    }

    [DataContract]
    public class UserNakBody : NakBody
    {
        [DataMember(Name = "USER")]
        public string Username { get; set; }
    }

    [DataContract]
    public class LoginBody : RegisterBody
    {
        [DataMember(Name = "VER")]
        public string Version { get; set; }
    }

    [DataContract]
    public class MessageOutBody : MessageBody
    {
        [DataMember(Name = "MESSAGE_ID")]
        public int MessageID { get; set; }

        [DataMember(Name = "PRIVATE")]
        public string Recipient { get; set; }

        [DataMember(Name = "MESSAGE")]
        public string Message { get; set; }
    }

    [DataContract]
    public class MessageOutAckBody : MessageBody
    {
        [DataMember(Name = "MESSAGE_ID")]
        public int MessageID { get; set; }
    }

    [DataContract]
    public class MessageOutNakBody : NakBody
    {
        [DataMember(Name = "MESSAGE_ID")]
        public int MessageID { get; set; }
    }

    [DataContract]
    public class MessageInBody : MessageBody
    {
        [DataMember(Name = "MESSAGE")]
        public string Message { get; set; }

        [DataMember(Name = "USER")]
        public string Sender { get; set; }

        [DataMember(Name = "ACTION")]
        public string Action { get; set; }
    }

    [DataContract]
    public class StatusBody : MessageBody
    {
        [DataMember(Name = "STATUS")]
        public string Status { get; set; }

        [DataMember(Name = "DESCRIPTION")]
        public string Description { get; set; }
    }

    [DataContract]
    public class ListUsersAckBody : MessageBody
    {
        [DataMember(Name = "USERS")]
        public string[] Users { get; set; }
    }

#endregion


}
